import numpy as np


def color_brew(n):
    """Generate n colors with equally spaced hues.
    :param n: The number of colors required.
    :type  n: int
    :returns: color_list: List of n tuples of form (R, G, B) being the components of each color.
    """
    color_list = []

    # Initialize saturation & value; calculate chroma & value shift
    s, v = 0.75, 0.9
    c = s * v
    m = v - c

    for h in np.arange(25, 385, 360. / n).astype(int):
        # Calculate some intermediate values
        h_bar = h / 60.
        x = c * (1 - abs((h_bar % 2) - 1))
        # Initialize RGB with same hue & chroma as our color
        rgb = [(c, x, 0),
               (x, c, 0),
               (0, c, x),
               (0, x, c),
               (x, 0, c),
               (c, 0, x),
               (c, x, 0)]
        r, g, b = rgb[int(h_bar)]
        # Shift the initial RGB values to match value and store
        rgb = [(int(255 * (r + m))),
               (int(255 * (g + m))),
               (int(255 * (b + m)))]
        color_list.append(rgb)

    return color_list


def get_color(target, classes=None):
    """根据节点的预测值分配颜色

    对分类任务，给clases中的每个标签分配一个颜色，返回target中数量最多的标签对应的颜色
    对回归任务，按照 target 的范围(min, max)，给 target.mean() 分配一个颜色

    :param target: 节点中样本的标签序列
    :type  target: np.ndarray
    :param classes: 对分类任务是包含所有标签的数组，对回归任务是None
    :type  classes:  np.narray or None
    """
    num_color = 1 if classes is None else classes.size
    color_list = color_brew(num_color)
    if classes is None:  # 回归任务
        value = np.mean(target)
        bounds = (target.min(), target.max())
        alpha = (value - bounds[0]) / (bounds[1] - bounds[0] + 1e-7)
        color = color_list[0]
    else:  # 分类器
        value = np.array([np.sum(target == label) for label in classes])
        label = classes[np.argmax(value)]
        color = color_list[label]
        value = value / value.sum()
        sorted_values = sorted(value, reverse=True)
        if len(sorted_values) == 1:
            alpha = 0
        else:
            alpha = (sorted_values[0] - sorted_values[1]) / (1 - sorted_values[1])

    alpha = float(alpha)
    # compute the color as alpha against white
    color = [int(round(alpha * c + (1 - alpha) * 255, 0)) for c in color]
    # Return html color code in #RRGGBB format
    return '#%2x%2x%2x' % tuple(color)


class Container():
    """一个有序的容器,可以放固定数量的满足条件的元素到里面

    用 put 方法放元素进去，会用 check 方法自动判断元素是否满足条件，满足条件
    的话会自动把元素插入到合适的位置，容器内按照元素的 value 从小到大排序。
    容器的容量由参数 max_size 确定
    """

    def __init__(self, max_size=10, reverse=False, retain_big=True):
        self.max_size = max_size
        self.__items = []
        self.reverse = reverse
        if retain_big:
            self.pop_func = self.__pop_min
        else:
            self.pop_func = self.__pop_max

    def __get_value__(self, item):
        return item

    def __check__(self, item):
        return True

        # 判断 item 是否满足加入容器的条件
        # 默认的条件是 item 的 value 要大于等于阈值
        threshhold = 4
        value = self.__get_value__(item)
        return True if value >= threshhold else False

    def __get_value(self, i):
        assert i < self.size, 'i 超出'
        return self.__get_value__(self.__items[i])

    @property
    def size(self):
        return len(self.__items)

    @property
    def max_value(self):
        return self.__get_value__(self.__items[-1])

    @property
    def min_value(self):
        return self.__get_value__(self.__items[0])

    @property
    def items(self):
        if self.reverse:
            return self.__items[::-1]
        else:
            return self.__items

    def __pop_min(self, value):
        # 容器要保留大值
        if value > self.min_value:
            del(self.__items[0])

    def __pop_max(self, value):
        # 容器要保留小值
        if value < self.max_value:
            del(self.__items[-1])

    def put(self, item):
        value = self.__get_value__(item)
        if self.__check__(item) is False:
            return

        # 处理容器已满的情况, 删去最大项
        if self.size == self.max_size:
            self.pop_func(value)
            if self.size == self.max_size:
                return

        # 按照 value 的大小插入到合适的位置
        # 用二分法找位置
        # 从小到大
        size = self.size
        mid = size // 2
        while 1:
            if size == 0:
                self.__items.insert(mid, item)
                break
            else:
                v = self.__get_value(mid)
                if value == v:
                    self.__items.insert(mid, item)
                    break
                elif value < v:
                    size = size // 2
                    mid = mid - size + size // 2
                else:
                    size = size - 1 - size // 2
                    mid = mid + 1 + size // 2

    def empty(self):
        self.__items.clear()
